/*
 *  Copyright (C) 2011 JiangHongTiao <jjurco.sk_gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package sk.lieskove.jianghongtiao.aresapi.websearch;

import java.io.IOException;
import java.util.Iterator;
import java.util.UUID;
import javax.xml.parsers.ParserConfigurationException;
import org.joda.time.DateTime;
import org.joda.time.IllegalFieldValueException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import sk.lieskove.jianghongtiao.aresapi.response.obj.AresOdpovedi;
import sk.lieskove.jianghongtiao.common.utils.PropertiesUtils;
import sk.lieskove.jianghongtiao.common.utils.xml.ProcessXML;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import sk.lieskove.jianghongtiao.common.persistence.Persist;
import sk.lieskove.jianghongtiao.multithreaddownloader.MultiThreadDownloadManager;
import sk.lieskove.jianghongtiao.multithreaddownloader.MultiThreadDownloader;
import sk.lieskove.jianghongtiao.multithreaddownloader.document.RemoteDocument;


/**
 * Class for searching for people in ARES throw web page. This type of search
 * for economic subjects does not have API, it have to be throw GET method
 * and then parse returned XML file.
 * Address for searching on web is: http://wwwinfo.mfcr.cz/ares/ares_fo.html.cz
 * You can search for subject by name, address, birth day or PO/OR number. When
 * birth number is specified, other parameters don't need to be specified. Search
 * is then based only on birth number, other (used) parameters are ignored.
 *
 * Date of create: May 15, 2011
 *
 * @author JiangHongTiao <jjurco.sk_gmail.com>
 * @version 2011.0515
 */
public class AresFindPeopleImpl implements AresFindPeople {

    Logger log = Logger.getLogger(AresFindPeopleImpl.class.getName());
    private String URL_ENCODING = "ISO-8859-2";
    
    private String SERVICE_URL = "http://wwwinfo.mfcr.cz/cgi-bin/ares/ares_fo.cgi?";
    private String PARAM_jmeno = "&jmeno=";
    private String PARAM_rc = "&rc=";
    private String PARAM_obec = "&obec=";
    private String PARAM_psc = "&psc=";
    private String PARAM_maxpoc = "&maxpoc=";
    private String PARAM_adresa = "&adresa=";
    private String PARAM_cis_or = "&cis_or=";
    private String PARAM_cis_po = "&cis_po=";
    private String PARAM_setrid = "&setrid=";
    private String PARAM_xml = "&xml=";
    private String PARAM_jazyk = "&jazyk=";
    private String PARAM_cestina = "&cestina=";
    private String PARAM_angazma = "&angazma=";
    private String PARAM_angaz = "&angaz=";
    private PropertiesUtils properties = null;
    /**
     * jméno a příjmení osoby
     */
    private String jmeno = null;
    /**
     * celé rodné číslo bez lomítka (Při zadání RČ jsou ostatní vyhledávací kritéria nadbytečná.)
     * rc1	první, datumová část rodného čísla
     * rc2	druhá část rodného čísla
     * Poznámka: použití parametru pro zadání rodného čísla předpokládá buď parametr rc nebo oba parametry rc1 a rc2.
     */
    private String rc = null;
    /**
     * název obce
     */
    private String obec = null;
    /**
     * číslo PSČ
     */
    private String psc = null;
    /**
     * maximální počet vracených subjektů. Povolené hodnoty: 200, 500, 1000.
     */
    private MaxPoc maxpoc = null;
    /**
     * název ulice v adrese
     */
    private String adresa = null;
    /**
     * číslo orientační v adrese
     * Číslo orientačního, povoleny číslicové znaky, přípustný interval hodnot
     * 1a - 999z. (do 17.2.2010 byl integer na 4 znaky)
     */
    private String cis_or;
    /**
     * číslo popisné v adrese
     */
    private String cis_po = null;
    /**
     * Jak mají být setříděné vracené subjekty:
     * =ZADNE - žádné setřídění
     * =RC - setříděno dle rodného čísla
     * =JMENO - setříděno dle jména osoby
     */
    private Setrid setrid = null;
    /**
     * =0 - výstup bude v xml
     * =1 - výstup bude v html, transformace budou v prohlížeči na PC
     * =2 - výstup bude v html, transformace z xml proběhne na serveru
     */
    private XmlVystup xml = XmlVystup.XML;
    /**
     * =cz - html bude v češtině
     * =en - html bude v angličtině
     */
    private Jazyk jazyk = Jazyk.EN;
    /**
     * =cestina - jméno osoby je zadáno s diakritickými znaménky
     * (ve formuláři: Diakritika: česká), bez tohoto parametru
     * (tj. Diakritika: ASCII) se předpokládá, že ve jméně diakr. znaménka nejsou
     */
    private Diakritika cestina = null;
    /**
     * výběr dle kódů angažmá
     * =všechna - zobrazí všechny nalezené subjekty
     * =výběr - jen aktivní subjekty
     */
    private Angazma angazma = null;
    /**
     * kód angažmá. Při více angažmá se oddělují +.
     * Přehled kódů angažmá:
     * (200-238 jsou angažmá z OR, 1800-1843 jsou z ISPOZ)
     * =200 - Podnikatel z OR
     * =201 - Člen statutárního orgánu
     * =202 - Likvidátor
     * =203 - Prokurista
     * =204 - Člen dozorčí rady
     * =205 - Jediný akcionář
     * =206 - Člen družstva s vkladem
     * =207 - Společník bez vkladu
     * =208 - Společník s vkladem
     * =209 - Komplementář
     * =210 - Komanditista
     * =211 - Správce konkursu
     * =212 - Zástupce správce konkursu
     * =214 - Zakladatel o.p.s.
     * =215 - Zřizovatel odštěpného závodu
     * =219 - Podílník
     * =220 - Revizor
     * =221 - Zřizovatel nadace
     * =222 - Statutár - vedoucí odštěpného závodu
     * =223 - Vedoucí odštěpného závodu
     * =224 - Zvláštní správce konkursu
     * =225 - Předběžný správce konkursní podstaty
     * =227 - Člen statutárního orgánu společnosti
     * =228 - Člen statutárního orgánu komplementářů
     * =229 - Člen statutárního orgánu představenstva
     * =230 - Člen představenstva
     * =231 - Člen správní rady
     * =232 - Oprávněná osoba nadace
     * =233 - Oprávněná osoba nadačního fondu
     * =234 - Nástupce zřizovatele
     * =235 - Zřizovatel příspěvkové organizace
     * =236 - Člen sdružení
     * =237 - Člen statutárního orgánu zřizovatele - Z
     * =238 - Člen kontrolní komise - Z
     * =400 - Podnikatel z RŽP
     * =401 - Statutár
     * =402 - Vedoucí org. složky
     * =1800 - Podnikatel z ISPOZ
     * =1801 - Člen statutárního orgánu
     * =1802 - Odpovědný zástupce
     */
    private Set<Angaz> angaz = null;

    public AresFindPeopleImpl() {
        properties = new PropertiesUtils(AresFindPeopleImpl.class);
    }
//    
//    private boolean containsDiacritics(String string){
//        
//    }

    @Override
    public String getAdresa() {
        return adresa;
    }

    @Override
    public void setAdresa(String adresa) throws IllegalArgumentException {
        if (adresa != null && adresa.length() > 45) {
            throw new IllegalArgumentException("Address is too long");
        }
        this.adresa = adresa;
    }

    @Override
    public Angaz[] getAngaz() {
        return angaz.toArray(new Angaz[0]);
    }

    @Override
    public void setAngaz(Angaz[] angaz) {
        if (angaz == null) {
            this.angaz = null;
        } else {
            this.angaz = new TreeSet<Angaz>();
            for (int i = 0; i < angaz.length; i++) {
                Angaz angaz1 = angaz[i];
                this.angaz.add(angaz1);
            }
        }

    }

    @Override
    public Angazma getAngazma() {
        return angazma;
    }

    @Override
    public void setAngazma(Angazma angazma) {
        this.angazma = angazma;
    }

    @Override
    public Diakritika getCestina() {
        return cestina;
    }

    @Override
    public void setCestina(Diakritika cestina) {
        this.cestina = cestina;
    }

    @Override
    public String getCis_or() {
        return cis_or;
    }

    @Override
    public void setCis_or(String cis_or) throws IllegalArgumentException {
        //erase set number
        if (cis_or == null || cis_or.equals("")) {
            this.cis_or = null;
            return;
        }
        cis_or = cis_or.toLowerCase();
        //Číslo orientačního, povoleny číslicové znaky, přípustný interval hodnot
        //1a - 999z. (do 17.2.2010 byl integer na 4 znaky)
        Pattern p = Pattern.compile("[0-9]{1,3}[a-z]?");
        Matcher m = p.matcher(cis_or);
        if (!m.matches()) {
            throw new IllegalArgumentException(
                    "String is not in format: /[0-9]{1,3}[a-z]?/");
        }
        this.cis_or = cis_or;
    }

    @Override
    public String getCis_po() {
        return cis_po;
    }

    @Override
    public void setCis_po(String cis_po) throws IllegalArgumentException,
            NumberFormatException {
        //erase set number
        if (cis_po == null || cis_po.equals("")) {
            this.cis_po = null;
            return;
        }
        Integer cislo = Integer.valueOf(cis_po);
        if ((cislo < 1) || (cislo > 9999)) {
            throw new IllegalArgumentException("String is not in range: 1-9999");
        }
        this.cis_po = cis_po;
    }

    @Override
    public Jazyk getJazyk() {
        return jazyk;
    }

    @Override
    public void setJazyk(Jazyk jazyk) {
        this.jazyk = jazyk;
    }

    @Override
    public String getJmeno() {
        return jmeno;
    }

    @Override
    public void setJmeno(String jmeno) throws IllegalArgumentException {
        if (jmeno != null && jmeno.length() > 25) {
            throw new IllegalArgumentException("Name is too long");
        }
        this.jmeno = jmeno;
    }

    @Override
    public MaxPoc getMaxpoc() {
        return maxpoc;
    }

    @Override
    public void setMaxpoc(MaxPoc maxpoc) {
        this.maxpoc = maxpoc;
    }

    @Override
    public String getObec() {
        return obec;
    }

    @Override
    public void setObec(String obec) throws IllegalArgumentException {
        if (obec != null && obec.length() > 45) {
            throw new IllegalArgumentException("Township is too long");
        }
        this.obec = obec;
    }

    @Override
    public String getPsc() {
        return psc;
    }

    @Override
    public void setPsc(String psc) throws IllegalArgumentException {
        if (psc == null || psc.equals("")) {
            this.psc = null;
            return;
        }
        Pattern p = Pattern.compile("\\d{5}");
        Matcher m = p.matcher(psc);
        if (!m.matches()) {
            throw new IllegalArgumentException(
                    "String is not in format: \\d\\d\\d\\d\\d");
        }
        this.psc = psc;
    }

    @Override
    public String getRc() {
        return rc;
    }

    @Override
    public void setRc(String rcLoc) throws NumberFormatException,
            IllegalArgumentException,
            IllegalFieldValueException {
        if ((rcLoc == null) || (rcLoc.equals(""))) {
            rc = null;
        } else {

            //check for length 9 or 10
            if (!((rcLoc.length() == 9) || (rcLoc.length() == 10))) {
                throw new IllegalArgumentException("Birth number is not of length 9 or 10: '"
                        + rcLoc + "'");
            }
            //check only for digits in number
            Pattern p = Pattern.compile("[0-9]{9,10}");
            Matcher m = p.matcher(rcLoc);
            if (!m.matches()) {
                throw new IllegalArgumentException(
                        "String does not contains only numbers.");
            }
            //parse number
            Integer AA = Integer.parseInt(rcLoc.substring(0, 2));
            Integer BB = Integer.parseInt(rcLoc.substring(2, 4));
            Integer CC = Integer.parseInt(rcLoc.substring(4, 6));
            Integer DDD = Integer.parseInt(rcLoc.substring(0, 9));
            Integer E = null;

            //check for length 10 and year after 1954 or 9 and before 1954
            if (rcLoc.length() == 10 && AA > 53) {
                E = Integer.parseInt(rcLoc.substring(9, 10));
            } else {
                if (rcLoc.length() == 10 && AA <= 53) {
                    throw new NumberFormatException(
                            "Birth number is before year 54 and has 10 digits.");
                }
                if (rcLoc.length() == 9 && AA > 53) {
                    throw new NumberFormatException(
                            "Birth number is after year 54 and has 9 digits.");
                }
            }

            //check woman/man month
            if (!((BB >= 1 && BB <= 12) || (BB >= 51 && BB <= 62))) {
                throw new NumberFormatException(
                        "Month is not in range 1-12 or 51-62");
            }

            //try to make date to check if it is correct date
            Integer fullYear = 1900 + AA;
            if (rcLoc.length() == 10 && AA < 54) {
                fullYear = 2000 + AA;
            }
            Integer month = BB % 50;

            DateTime dateTime = new DateTime(fullYear + "-" + month + "-" + CC);
            System.out.println("DateTime: " + dateTime);
            //check for future numbers
            if (dateTime.isAfterNow()) {
                throw new FutureDateException("Date is from future.");
            }

            //check for DDD and E number
            if (E != null) {
                if (((DDD % 11) % 10) != E) {
                    throw new NumberFormatException(
                            "Last control number is not correct.");
                }
            }

            this.rc = rcLoc;
        }
    }

    @Override
    public void setRc(String predLomenom, String zaLomenom) {
        if ((predLomenom == null) || (zaLomenom == null) || (predLomenom.equals(
                "")) || (zaLomenom.equals(""))) {
            setRc(null);
        } else {
            setRc(predLomenom + zaLomenom);
        }
    }

    @Override
    public Setrid getSetrid() {
        return setrid;
    }

    @Override
    public void setSetrid(Setrid setrid) {
        this.setrid = setrid;
    }

    @Override
    public XmlVystup getXml() {
        return xml;
    }

    @Override
    public void setXml(XmlVystup xml) {
        this.xml = xml;
    }

    public String makeUrl() throws NoParameterSet {
        if ((jmeno == null) && (obec == null) && (adresa == null)
                && (rc == null)) {
            throw new NoParameterSet(
                    "Missing one of parameter: jmeno; obec; adresa; rc");
        }
        String result = "";
        if (rc != null) {
            result += PARAM_rc + rc;
        } else {
            if (jmeno != null) {
                try {
                    result += PARAM_jmeno + URLEncoder.encode(jmeno, URL_ENCODING);
                } catch (UnsupportedEncodingException ex) {
                    log.error("Unexpected exception thrown while trying to make "
                            + "URL encode with UTF-8 encoding: " + jmeno, ex);
                }
            }
            if (obec != null) {
                try {
                    result += PARAM_obec + URLEncoder.encode(obec, URL_ENCODING);
                } catch (UnsupportedEncodingException ex) {
                    log.error("Unexpected exception thrown while trying to make "
                            + "URL encode with UTF-8 encoding: " + obec, ex);
                }
            }
            if (psc != null) {
                result += PARAM_psc + psc;
            }
            if (maxpoc != null) {
                result += PARAM_maxpoc + maxpoc;
            }
            if (adresa != null) {
                try {
                    result += PARAM_adresa + URLEncoder.encode(adresa, URL_ENCODING);
                } catch (UnsupportedEncodingException ex) {
                    log.error("Unexpected exception thrown while trying to make "
                            + "URL encode with UTF-8 encoding: " + adresa, ex);
                }
            }
            if (cis_or != null) {
                result += PARAM_cis_or + cis_or;
            }
            if (cis_po != null) {
                result += PARAM_cis_po + cis_po;
            }
            if (setrid != null) {
                try {
                    result += PARAM_setrid + URLEncoder.encode(setrid.toString(),
                            URL_ENCODING);
                } catch (UnsupportedEncodingException ex) {
                    log.error("Unexpected exception thrown while trying to make "
                            + "URL encode with UTF-8 encoding: " + setrid.
                            toString(), ex);
                }
            }
            if (cestina != null) {
                try {
                    result += PARAM_cestina + URLEncoder.encode(
                            cestina.toString(), URL_ENCODING);
                } catch (UnsupportedEncodingException ex) {
                    log.error("Unexpected exception thrown while trying to make "
                            + "URL encode with UTF-8 encoding: " + cestina.
                            toString(), ex);
                }
            }
            if (angazma != null) {
                try {
                    result += PARAM_angazma + URLEncoder.encode(
                            angazma.toString(), URL_ENCODING);
                } catch (UnsupportedEncodingException ex) {
                    log.error("Unexpected exception thrown while trying to make "
                            + "URL encode with UTF-8 encoding: " + angazma.
                            toString(), ex);
                }
            }
            if ((angaz != null) && (!angaz.isEmpty())) {
                result += PARAM_angaz + getAngazAsString();
            }
        }
        if (xml != null) {
            result += PARAM_xml + xml;
        }
        if (jazyk != null) {
            result += PARAM_jazyk + jazyk;
        }
        if (result.length() > 1) {
            return SERVICE_URL + result.substring(1);
        } else {
            throw new NoParameterSet();
        }
    }

    private String getAngazAsString() {
        if ((angaz == null) || (angaz.isEmpty())) {
            return null;
        }
        String result = "";
        for (Iterator<Angaz> it = angaz.iterator(); it.hasNext();) {
            Angaz an = it.next();
            result += "+" + an;
        }
        return result.substring(1);
    }

    /**
     * get document from web or testing document
     *
     * @return document for parsing on successful download, or null on error during downloading file
     */
    private File getXmlDocument() {
        MultiThreadDownloadManager downloadManager = MultiThreadDownloader.getInstance();
        
        String runType = properties.getProperty("runType", "test");
        File xmlDocument = null;
        if (runType.equals("test")) {
            String testFile = properties.getProperty("ares-api-test-file");
            xmlDocument = new File(testFile);
        }
        if (runType.equals("onair")) {
            try {
                UUID link = downloadManager.addLink(new URL(makeUrl()), 100);
                RemoteDocument downloadPage = downloadManager.getDownload(link);
                if (downloadPage.getFile() == null) {
                    log.error("Cannot get file: '" + makeUrl() + "'. " + downloadPage.
                            getReturnCode().toString());
                }
                xmlDocument = downloadPage.getFile();
            } catch (MalformedURLException ex) {
                log.error("Error thrown while trying download file: '"
                        + makeUrl() + "'. URL has malformed format", ex);
            }
        }
        return xmlDocument;
    }

    @Override
    public AresOdpovedi find() {
        AresOdpovedi odpovedi = null;
        String docPath = "<file not retrieved>";
        try {
            File f = getXmlDocument();
            if (f != null) {
                docPath = f.getPath();
                Document openDocument = ProcessXML.openDocument(docPath);
                odpovedi = new AresOdpovedi(openDocument);

                Persist persist = Persist.getSingleton();
                persist.searchResults(odpovedi);
            }
            return odpovedi;
        } catch (SAXException ex) {
            log.error("SAX parsing exception. File '" + docPath
                    + "' has probably not correct format. ", ex);
        } catch (ParserConfigurationException ex) {
            log.error("Pasrser configuration is not correct. ", ex);
        } catch (IOException ex) {
            log.error("Error thrown while reading file: '" + docPath + "'.", ex);
        }
        return null;
    }

    @Override
    public AresOdpovedi findByName(String name) {
        setJmeno(name);
        return find();
    }
}
